package com.freeheart.aop.log.logscan;

import cn.hutool.core.util.ArrayUtil;
import com.freeheart.aop.log.LogScanManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 为ConditionOnLogScan注解提供是否加载Bean的逻辑判断
 * <p>
 * 如果Spring容器中并未标记：@LogScan主注解，或者标注了注解，但是属性名数组中不包含有效包路径，则不会加载
 * Advisor 的Bean，{@see LogScanAutoConfiguration}
 *
 * @author EmperorHeart
 */
public class LogScanCondition implements Condition {
    private final Logger logger = LoggerFactory.getLogger(LogScanCondition.class);

    /**
     * 在Spring中查看是否使用LogScan注解
     *
     * @param context
     * @param metadata
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        String[] names = beanFactory.getBeanNamesForAnnotation(LogScan.class);
        if (ArrayUtil.isEmpty(names)) {
            return false;
        }

        Set<String> validLogScanPath = new HashSet<>();
        for (String name : names) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
            addValidPath2Set(validLogScanPath, beanDefinition);
        }

        if (CollectionUtils.isEmpty(validLogScanPath)) {
            return false;
        }

        LogScanManager.registerBasePackages(validLogScanPath);

        return true;


    }

    /**
     * 将有效的包路径添加到logScanProperty中
     *
     * @param validLogScanPath
     * @param beanDefinition
     */
    private void addValidPath2Set(Set<String> validLogScanPath, BeanDefinition beanDefinition) {
        String[] basePackages = null;
        if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
            AnnotationMetadata logScabMetadata = annotatedBeanDefinition.getMetadata();
            Map<String, Object> annotationAttributes = logScabMetadata.getAnnotationAttributes(LogScan.class.getName());
            basePackages = (String[]) annotationAttributes.get("value");
        } else {
            // 使用反射
            String beanClassName = beanDefinition.getBeanClassName();
            try {
                LogScan logScan = AnnotationUtils.findAnnotation(Class.forName(beanClassName), LogScan.class);
                basePackages = logScan.value();
            } catch (ClassNotFoundException e) {
                logger.error("Class {} Not exists, which may affect the logging aspect", beanClassName, e);
            }
        }
        checkAndAddValidPath(validLogScanPath, basePackages);
    }

    /**
     * 检查包路径是否可用并添加到提供集合中
     *
     * @param validLogScanPath
     * @param value
     */
    private void checkAndAddValidPath(Set<String> validLogScanPath, String[] value) {
        if (value == null || value.length == 0) {
            return;
        }

        for (String basePackage : value) {
            if (isPackagePresent(basePackage)) {
                validLogScanPath.add(basePackage);
            }
        }

    }

    /**
     * 判断包路径是否存在
     *
     * @param basePackage
     * @return
     */
    public boolean isPackagePresent(String basePackage) {
        try {
            Resource[] resources = new PathMatchingResourcePatternResolver()
                    .getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                            ClassUtils.convertClassNameToResourcePath(basePackage) + "/**/*.class");
            return resources.length > 0;
        } catch (IOException e) {
            logger.warn("路径：{}不存在，已进行忽略", basePackage);
            return false;
        }
    }

}
